Trò chơi Angry Birds trong UNITY Engine
31.717 lượt xem;
1 using UnityEngine;
2 using UnityEditor;
3 using System.Collections;
4 using System.Linq;
5 using System.Collections.Generic;
6 using System.IO;
7
8
9
10 [CustomEditor( typeof( GoDummyPath ) )]
11 public class GoDummyPathEditor : Editor
12 {
13 private GoDummyPath _target;
14 private GUIStyle _labelStyle;
15 private GUIStyle _indexStyle;
16
17 private int _insertIndex = 0;
18 private float _snapDistance = 5f;
19 private bool _showNodeDetails;
20 private bool _fileLoadSaveDetails;
21 private int _selectedNodeIndex = -1;
22
23
24 #region Monobehaviour and Editor
25
26 void OnEnable()
27 {
28 // setup the font for the 'begin' 'end' text
29 _labelStyle = new GUIStyle();
30 _labelStyle.fontStyle = FontStyle.Bold;
31 _labelStyle.normal.textColor = Color.white;
32 _labelStyle.fontSize = 16;
33
34 _indexStyle = new GUIStyle();
35 _indexStyle.fontStyle = FontStyle.Bold;
36 _indexStyle.normal.textColor = Color.white;
37 _indexStyle.fontSize = 12;
38
39 _target = (GoDummyPath)target;
40 }
41
42
43 public override void OnInspectorGUI()
44 {
45 // what kind of handles shall we use?
46 EditorGUILayout.BeginHorizontal();
47 EditorGUILayout.PrefixLabel( "Use Standard Handles" );
48 _target.useStandardHandles = EditorGUILayout.Toggle( _target.useStandardHandles );
49 EditorGUILayout.EndHorizontal();
50
51
52 // path name:
53 EditorGUILayout.BeginHorizontal();
54 EditorGUILayout.PrefixLabel( "Route Name" );
55 _target.pathName = EditorGUILayout.TextField( _target.pathName );
56 EditorGUILayout.EndHorizontal();
57
58 if( _target.pathName == string.Empty )
59 _target.pathName = "route" + Random.Range( 1, 100000 );
60
61
62 // path color:
63 EditorGUILayout.BeginHorizontal();
64 EditorGUILayout.PrefixLabel( "Route Color" );
65 _target.pathColor = EditorGUILayout.ColorField( _target.pathColor );
66 EditorGUILayout.EndHorizontal();
67
68
69 // force straight lines:
70 EditorGUILayout.BeginHorizontal();
71 EditorGUILayout.PrefixLabel( "Force Straight Line Path" );
72 _target.forceStraightLinePath = EditorGUILayout.Toggle( _target.forceStraightLinePath );
73 EditorGUILayout.EndHorizontal();
74
75
76 // resolution
77 EditorGUILayout.BeginHorizontal();
78 EditorGUILayout.PrefixLabel( "Editor Drawing Resolution" );
79 _target.pathResolution = EditorGUILayout.IntSlider( _target.pathResolution, 2, 100 );
80 EditorGUILayout.EndHorizontal();
81
82
83 EditorGUILayout.Separator();
84
85
86 // insert node - we need 3 or more nodes for insert to make sense
87 if( _target.nodes.Count > 2 )
88 {
89 EditorGUILayout.BeginHorizontal();
90 EditorGUILayout.PrefixLabel( "Insert Node" );
91 _insertIndex = EditorGUILayout.IntField( _insertIndex );
92 if( GUILayout.Button( "Insert" ) )
93 {
94 // validate the index
95 if( _insertIndex >= 0 && _insertIndex < _target.nodes.Count )
96 {
97 // insert the node offsetting it a bit from the previous node
98 var copyNodeIndex = _insertIndex == 0 ? 0 : _insertIndex;
99 var copyNode = _target.nodes[copyNodeIndex];
100 copyNode.x += 10;
101 copyNode.z += 10;
102
103 insertNodeAtIndex( copyNode, _insertIndex );
104 }
105 }
106 EditorGUILayout.EndHorizontal();
107 }
108
109
110 // close route?
111 if( GUILayout.Button( "Close Path" ) )
112 {
113 Undo.RecordObject( _target, "Path Vector Changed" );
114 closeRoute();
115 GUI.changed = true;
116 }
117
118
119 // shift the start point to the origin
120 if( GUILayout.Button( "Shift Path to Start at Origin" ) )
121 {
122 Undo.RecordObject( _target, "Path Vector Changed" );
123
124 var offset = Vector3.zero;
125
126 // see what kind of path we are. the simplest case is just a straight line
127 var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
128 if( path.splineType == GoSplineType.StraightLine || _target.nodes.Count < 5 )
129 offset = Vector3.zero - _target.nodes[0];
130 else
131 offset = Vector3.zero - _target.nodes[1];
132
133 for( var i = 0; i < _target.nodes.Count; i++ )
134 _target.nodes[i] += offset;
135
136 GUI.changed = true;
137 }
138
139
140 // reverse
141 if( GUILayout.Button( "Reverse Path" ) )
142 {
143 Undo.RecordObject( _target, "Path Vector Changed" );
144 _target.nodes.Reverse();
145 GUI.changed = true;
146 }
147
148
149 // persist to disk
150 EditorGUILayout.Space();
151 EditorGUILayout.LabelField( "Save to/Read from Disk" );
152
153 EditorGUILayout.Space();
154 EditorGUILayout.BeginHorizontal();
155 EditorGUILayout.PrefixLabel( "Serialize and Save Path" );
156 if( GUILayout.Button( "Save" ) )
157 {
158 var path = EditorUtility.SaveFilePanel( "Save path", Application.dataPath + "/StreamingAssets", _target.pathName + ".asset", "asset" );
159 if( path != string.Empty )
160 {
161 persistRouteToDisk( path );
162
163 // fetch the filename and set it as the routeName
164 _target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
165 GUI.changed = true;
166 }
167 }
168 EditorGUILayout.EndHorizontal();
169
170
171 // load from disk
172 EditorGUILayout.BeginHorizontal();
173 EditorGUILayout.PrefixLabel( "Load saved path" );
174 if( GUILayout.Button( "Load" ) )
175 {
176 var path = EditorUtility.OpenFilePanel( "Choose path to load", Path.Combine( Application.dataPath, "StreamingAssets" ), "asset" );
177 if( path != string.Empty )
178 {
179 if( !File.Exists( path ) )
180 {
181 EditorUtility.DisplayDialog( "File does not exist", "Path couldn't find the file you specified", "Close" );
182 }
183 else
184 {
185 _target.nodes = GoSpline.bytesToVector3List( File.ReadAllBytes( path ) );
186 _target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
187 GUI.changed = true;
188 }
189 }
190 }
191 EditorGUILayout.EndHorizontal();
192
193
194 // node display
195 EditorGUILayout.Space();
196 _showNodeDetails = EditorGUILayout.Foldout( _showNodeDetails, "Show Node Values" );
197 if( _showNodeDetails )
198 {
199 EditorGUI.indentLevel++;
200 for( int i = 0; i < _target.nodes.Count; i++ )
201 _target.nodes[i] = EditorGUILayout.Vector3Field( "Node " + ( i + 1 ), _target.nodes[i] );
202 EditorGUI.indentLevel--;
203 }
204
205
206 // instructions
207 EditorGUILayout.Space();
208 EditorGUILayout.HelpBox( "While dragging a node, hold down Ctrl and slowly move the cursor to snap to a nearby point\n\n" +
209 "Click the 'Close Path' button to add a new node that will close out the current path.\n\n" +
210 "Hold Command while dragging a node to snap in 5 point increments\n\n" +
211 "Double click to add a new node at the end of the path\n\n" +
212 "Hold down alt while adding a node to prepend the new node at the front of the route\n\n" +
213 "Press delete or backspace to delete the selected node\n\n" +
214 "NOTE: make sure you have the pan tool selected while editing paths", MessageType.None );
215
216
217 // update and redraw:
218 if( GUI.changed )
219 {
220 EditorUtility.SetDirty( _target );
221 Repaint();
222 }
223 }
224
225
226 void OnSceneGUI()
227 {
228 if( !_target.gameObject.activeSelf )
229 return;
230
231 // handle current selection and node addition via double click or ctrl click
232 if( Event.current.type == EventType.mouseDown )
233 {
234 var nearestIndex = getNearestNodeForMousePosition( Event.current.mousePosition );
235 _selectedNodeIndex = nearestIndex;
236
237 // double click to add
238 if( Event.current.clickCount > 1 )
239 {
240 var translatedPoint = HandleUtility.GUIPointToWorldRay( Event.current.mousePosition )
241 .GetPoint( ( _target.transform.position - Camera.current.transform.position ).magnitude );
242
243 Undo.RecordObject( _target, "Path Node Added" );
244
245 // if alt is down then prepend the node to the beginning
246 if( Event.current.alt )
247 insertNodeAtIndex( translatedPoint, 0 );
248 else
249 appendNodeAtPoint( translatedPoint );
250 }
251 }
252
253
254 if( _selectedNodeIndex >= 0 )
255 {
256 // shall we delete the selected node?
257 if( Event.current.keyCode == KeyCode.Delete || Event.current.keyCode == KeyCode.Backspace )
258 {
259 if (_target.nodes.Count > 2) {
260 Undo.RecordObject( _target, "Path Node Deleted" );
261 Event.current.Use();
262 removeNodeAtIndex( _selectedNodeIndex );
263 _selectedNodeIndex = -1;
264 }
265 }
266 }
267
268
269 if( _target.nodes.Count > 1 )
270 {
271 // allow path adjustment undo:
272 Undo.RecordObject( _target, "Path Vector Changed" );
273
274 // path begin and end labels or just one if the path is closed
275 if( Vector3.Distance( _target.nodes[0], _target.nodes[_target.nodes.Count - 1] ) == 0 )
276 {
277 Handles.Label( _target.nodes[0], " Begin and End", _labelStyle );
278 }
279 else
280 {
281 Handles.Label( _target.nodes[0], " Begin", _labelStyle );
282 Handles.Label( _target.nodes[_target.nodes.Count - 1], " End", _labelStyle );
283 }
284
285 // draw the handles, arrows and lines
286 drawRoute();
287
288 for( var i = 0; i < _target.nodes.Count; i++ )
289 {
290 Handles.color = _target.pathColor;
291
292 // dont label the first and last nodes
293 if( i > 0 && i < _target.nodes.Count - 1 )
294 Handles.Label( _target.nodes[i] + new Vector3( 3f, 0, 1.5f ), i.ToString(), _indexStyle );
295
296 Handles.color = Color.white;
297 if( _target.useStandardHandles )
298 {
299 _target.nodes[i] = Handles.PositionHandle( _target.nodes[i], Quaternion.identity );
300 }
301 else
302 {
303 // how big shall we draw the handles?
304 var distanceToTarget = Vector3.Distance( SceneView.lastActiveSceneView.camera.transform.position, _target.transform.position );
305 distanceToTarget = Mathf.Abs( distanceToTarget );
306 var handleSize = Mathf.Ceil( distanceToTarget / 75 );
307
308 _target.nodes[i] = Handles.FreeMoveHandle( _target.nodes[i],
309 Quaternion.identity,
310 handleSize,
311 new Vector3( 5, 0, 5 ),
312 Handles.SphereCap );
313 }
314
315
316 // should we snap? we need at least 4 nodes because we dont snap to the previous and next nodes
317 if( Event.current.control && _target.nodes.Count > 3 )
318 {
319 // dont even bother checking for snapping to the previous/next nodes
320 var index = getNearestNode( _target.nodes[i], i, i + 1, i - 1 );
321 var nearest = _target.nodes[index];
322 var distanceToNearestNode = Vector3.Distance( nearest, _target.nodes[i] );
323
324 // is it close enough to snap?
325 if( distanceToNearestNode <= _snapDistance )
326 {
327 GUI.changed = true;
328 _target.nodes[i] = nearest;
329 }
330 else if( distanceToNearestNode <= _snapDistance * 2 )
331 {
332 // show which nodes are getting close enough to snap to
333 var color = Color.red;
334 color.a = 0.3f;
335 Handles.color = color;
336 Handles.SphereCap( 0, _target.nodes[i], Quaternion.identity, _snapDistance * 2 );
337 //Handles.DrawWireDisc( _target.nodes[i], Vector3.up, _snapDistance );
338 Handles.color = Color.white;
339 }
340 }
341 } // end for
342
343
344 if( GUI.changed )
345 {
346 Repaint();
347 EditorUtility.SetDirty( _target );
348 }
349 } // end if
350 }
351
352 #endregion
353
354
355 #region Private methods
356
357 private void appendNodeAtPoint( Vector3 node )
358 {
359 _target.nodes.Add( node );
360
361 GUI.changed = true;
362 }
363
364
365 private void removeNodeAtIndex( int index )
366 {
367 if( index >= _target.nodes.Count || index < 0 )
368 return;
369
370 _target.nodes.RemoveAt( index );
371
372 GUI.changed = true;
373 }
374
375
376 private void insertNodeAtIndex( Vector3 node, int index )
377 {
378 // validate the index
379 if( index >= 0 && index < _target.nodes.Count )
380 {
381 _target.nodes.Insert( index, node );
382
383 GUI.changed = true;
384 }
385 }
386
387
388 private void drawArrowBetweenPoints( Vector3 point1, Vector3 point2 )
389 {
390 // no need to draw arrows for tiny segments
391 var distance = Vector3.Distance( point1, point2 );
392 if( distance < 40 )
393 return;
394
395 // we dont want to be exactly in the middle so we offset the length of the arrow
396 var lerpModifier = ( distance * 0.5f - 25 ) / distance;
397
398 Handles.color = _target.pathColor;
399
400 // get the midpoint between the 2 points
401 var dir = Vector3.Lerp( point1, point2, lerpModifier );
402 var quat = Quaternion.LookRotation( point2 - point1 );
403 Handles.ArrowCap( 0, dir, quat, 25 );
404
405 Handles.color = Color.white;
406 }
407
408
409 private int getNearestNode( Vector3 pos, params int[] excludeNodes )
410 {
411 var excludeNodesList = new System.Collections.Generic.List<int>( excludeNodes );
412 var bestDistance = float.MaxValue;
413 var index = -1;
414
415 var distance = float.MaxValue;
416 for( var i = _target.nodes.Count - 1; i >= 0; i-- )
417 {
418 if( excludeNodesList.Contains( i ) )
419 continue;
420
421 distance = Vector3.Distance( pos, _target.nodes[i] );
422 if( distance < bestDistance )
423 {
424 bestDistance = distance;
425 index = i;
426 }
427 }
428 return index;
429 }
430
431
432 private int getNearestNodeForMousePosition( Vector3 mousePos )
433 {
434 var bestDistance = float.MaxValue;
435 var index = -1;
436
437 var distance = float.MaxValue;
438 for( var i = _target.nodes.Count - 1; i >= 0; i-- )
439 {
440 var nodeToGui = HandleUtility.WorldToGUIPoint( _target.nodes[i] );
441 distance = Vector2.Distance( nodeToGui, mousePos );
442
443 if( distance < bestDistance )
444 {
445 bestDistance = distance;
446 index = i;
447 }
448 }
449
450 // make sure we are close enough to a node
451 if( bestDistance < 10 )
452 return index;
453 return -1;
454 }
455
456
457 private void closeRoute()
458 {
459 // we will use the GoSpline class to handle the dirtywork of closing the path
460 var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
461 path.closePath();
462
463 _target.nodes = path.nodes;
464
465 GUI.changed = true;
466 }
467
468
469 private void persistRouteToDisk( string path )
470 {
471 var bytes = new List<byte>();
472
473 foreach( var vec in _target.nodes )
474 {
475 bytes.AddRange( System.BitConverter.GetBytes( vec.x ) );
476 bytes.AddRange( System.BitConverter.GetBytes( vec.y ) );
477 bytes.AddRange( System.BitConverter.GetBytes( vec.z ) );
478 }
479
480 File.WriteAllBytes( path, bytes.ToArray() );
481 }
482
483
484 private void drawRoute()
485 {
486 // if we are forcing straight lines just use this setup
487 if( _target.forceStraightLinePath )
488 {
489 // draw just the route here and optional arrows
490 for( var i = 0; i < _target.nodes.Count; i++ )
491 {
492 Handles.color = _target.pathColor;
493 if( i < _target.nodes.Count - 1 )
494 {
495 Handles.DrawLine( _target.nodes[i], _target.nodes[i + 1] );
496 drawArrowBetweenPoints( _target.nodes[i], _target.nodes[i + 1] );
497 }
498 }
499 }
500 }
501
502 #endregion
503
504 }
2 using UnityEditor;
3 using System.Collections;
4 using System.Linq;
5 using System.Collections.Generic;
6 using System.IO;
7
8
9
10 [CustomEditor( typeof( GoDummyPath ) )]
11 public class GoDummyPathEditor : Editor
12 {
13 private GoDummyPath _target;
14 private GUIStyle _labelStyle;
15 private GUIStyle _indexStyle;
16
17 private int _insertIndex = 0;
18 private float _snapDistance = 5f;
19 private bool _showNodeDetails;
20 private bool _fileLoadSaveDetails;
21 private int _selectedNodeIndex = -1;
22
23
24 #region Monobehaviour and Editor
25
26 void OnEnable()
27 {
28 // setup the font for the 'begin' 'end' text
29 _labelStyle = new GUIStyle();
30 _labelStyle.fontStyle = FontStyle.Bold;
31 _labelStyle.normal.textColor = Color.white;
32 _labelStyle.fontSize = 16;
33
34 _indexStyle = new GUIStyle();
35 _indexStyle.fontStyle = FontStyle.Bold;
36 _indexStyle.normal.textColor = Color.white;
37 _indexStyle.fontSize = 12;
38
39 _target = (GoDummyPath)target;
40 }
41
42
43 public override void OnInspectorGUI()
44 {
45 // what kind of handles shall we use?
46 EditorGUILayout.BeginHorizontal();
47 EditorGUILayout.PrefixLabel( "Use Standard Handles" );
48 _target.useStandardHandles = EditorGUILayout.Toggle( _target.useStandardHandles );
49 EditorGUILayout.EndHorizontal();
50
51
52 // path name:
53 EditorGUILayout.BeginHorizontal();
54 EditorGUILayout.PrefixLabel( "Route Name" );
55 _target.pathName = EditorGUILayout.TextField( _target.pathName );
56 EditorGUILayout.EndHorizontal();
57
58 if( _target.pathName == string.Empty )
59 _target.pathName = "route" + Random.Range( 1, 100000 );
60
61
62 // path color:
63 EditorGUILayout.BeginHorizontal();
64 EditorGUILayout.PrefixLabel( "Route Color" );
65 _target.pathColor = EditorGUILayout.ColorField( _target.pathColor );
66 EditorGUILayout.EndHorizontal();
67
68
69 // force straight lines:
70 EditorGUILayout.BeginHorizontal();
71 EditorGUILayout.PrefixLabel( "Force Straight Line Path" );
72 _target.forceStraightLinePath = EditorGUILayout.Toggle( _target.forceStraightLinePath );
73 EditorGUILayout.EndHorizontal();
74
75
76 // resolution
77 EditorGUILayout.BeginHorizontal();
78 EditorGUILayout.PrefixLabel( "Editor Drawing Resolution" );
79 _target.pathResolution = EditorGUILayout.IntSlider( _target.pathResolution, 2, 100 );
80 EditorGUILayout.EndHorizontal();
81
82
83 EditorGUILayout.Separator();
84
85
86 // insert node - we need 3 or more nodes for insert to make sense
87 if( _target.nodes.Count > 2 )
88 {
89 EditorGUILayout.BeginHorizontal();
90 EditorGUILayout.PrefixLabel( "Insert Node" );
91 _insertIndex = EditorGUILayout.IntField( _insertIndex );
92 if( GUILayout.Button( "Insert" ) )
93 {
94 // validate the index
95 if( _insertIndex >= 0 && _insertIndex < _target.nodes.Count )
96 {
97 // insert the node offsetting it a bit from the previous node
98 var copyNodeIndex = _insertIndex == 0 ? 0 : _insertIndex;
99 var copyNode = _target.nodes[copyNodeIndex];
100 copyNode.x += 10;
101 copyNode.z += 10;
102
103 insertNodeAtIndex( copyNode, _insertIndex );
104 }
105 }
106 EditorGUILayout.EndHorizontal();
107 }
108
109
110 // close route?
111 if( GUILayout.Button( "Close Path" ) )
112 {
113 Undo.RecordObject( _target, "Path Vector Changed" );
114 closeRoute();
115 GUI.changed = true;
116 }
117
118
119 // shift the start point to the origin
120 if( GUILayout.Button( "Shift Path to Start at Origin" ) )
121 {
122 Undo.RecordObject( _target, "Path Vector Changed" );
123
124 var offset = Vector3.zero;
125
126 // see what kind of path we are. the simplest case is just a straight line
127 var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
128 if( path.splineType == GoSplineType.StraightLine || _target.nodes.Count < 5 )
129 offset = Vector3.zero - _target.nodes[0];
130 else
131 offset = Vector3.zero - _target.nodes[1];
132
133 for( var i = 0; i < _target.nodes.Count; i++ )
134 _target.nodes[i] += offset;
135
136 GUI.changed = true;
137 }
138
139
140 // reverse
141 if( GUILayout.Button( "Reverse Path" ) )
142 {
143 Undo.RecordObject( _target, "Path Vector Changed" );
144 _target.nodes.Reverse();
145 GUI.changed = true;
146 }
147
148
149 // persist to disk
150 EditorGUILayout.Space();
151 EditorGUILayout.LabelField( "Save to/Read from Disk" );
152
153 EditorGUILayout.Space();
154 EditorGUILayout.BeginHorizontal();
155 EditorGUILayout.PrefixLabel( "Serialize and Save Path" );
156 if( GUILayout.Button( "Save" ) )
157 {
158 var path = EditorUtility.SaveFilePanel( "Save path", Application.dataPath + "/StreamingAssets", _target.pathName + ".asset", "asset" );
159 if( path != string.Empty )
160 {
161 persistRouteToDisk( path );
162
163 // fetch the filename and set it as the routeName
164 _target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
165 GUI.changed = true;
166 }
167 }
168 EditorGUILayout.EndHorizontal();
169
170
171 // load from disk
172 EditorGUILayout.BeginHorizontal();
173 EditorGUILayout.PrefixLabel( "Load saved path" );
174 if( GUILayout.Button( "Load" ) )
175 {
176 var path = EditorUtility.OpenFilePanel( "Choose path to load", Path.Combine( Application.dataPath, "StreamingAssets" ), "asset" );
177 if( path != string.Empty )
178 {
179 if( !File.Exists( path ) )
180 {
181 EditorUtility.DisplayDialog( "File does not exist", "Path couldn't find the file you specified", "Close" );
182 }
183 else
184 {
185 _target.nodes = GoSpline.bytesToVector3List( File.ReadAllBytes( path ) );
186 _target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
187 GUI.changed = true;
188 }
189 }
190 }
191 EditorGUILayout.EndHorizontal();
192
193
194 // node display
195 EditorGUILayout.Space();
196 _showNodeDetails = EditorGUILayout.Foldout( _showNodeDetails, "Show Node Values" );
197 if( _showNodeDetails )
198 {
199 EditorGUI.indentLevel++;
200 for( int i = 0; i < _target.nodes.Count; i++ )
201 _target.nodes[i] = EditorGUILayout.Vector3Field( "Node " + ( i + 1 ), _target.nodes[i] );
202 EditorGUI.indentLevel--;
203 }
204
205
206 // instructions
207 EditorGUILayout.Space();
208 EditorGUILayout.HelpBox( "While dragging a node, hold down Ctrl and slowly move the cursor to snap to a nearby point\n\n" +
209 "Click the 'Close Path' button to add a new node that will close out the current path.\n\n" +
210 "Hold Command while dragging a node to snap in 5 point increments\n\n" +
211 "Double click to add a new node at the end of the path\n\n" +
212 "Hold down alt while adding a node to prepend the new node at the front of the route\n\n" +
213 "Press delete or backspace to delete the selected node\n\n" +
214 "NOTE: make sure you have the pan tool selected while editing paths", MessageType.None );
215
216
217 // update and redraw:
218 if( GUI.changed )
219 {
220 EditorUtility.SetDirty( _target );
221 Repaint();
222 }
223 }
224
225
226 void OnSceneGUI()
227 {
228 if( !_target.gameObject.activeSelf )
229 return;
230
231 // handle current selection and node addition via double click or ctrl click
232 if( Event.current.type == EventType.mouseDown )
233 {
234 var nearestIndex = getNearestNodeForMousePosition( Event.current.mousePosition );
235 _selectedNodeIndex = nearestIndex;
236
237 // double click to add
238 if( Event.current.clickCount > 1 )
239 {
240 var translatedPoint = HandleUtility.GUIPointToWorldRay( Event.current.mousePosition )
241 .GetPoint( ( _target.transform.position - Camera.current.transform.position ).magnitude );
242
243 Undo.RecordObject( _target, "Path Node Added" );
244
245 // if alt is down then prepend the node to the beginning
246 if( Event.current.alt )
247 insertNodeAtIndex( translatedPoint, 0 );
248 else
249 appendNodeAtPoint( translatedPoint );
250 }
251 }
252
253
254 if( _selectedNodeIndex >= 0 )
255 {
256 // shall we delete the selected node?
257 if( Event.current.keyCode == KeyCode.Delete || Event.current.keyCode == KeyCode.Backspace )
258 {
259 if (_target.nodes.Count > 2) {
260 Undo.RecordObject( _target, "Path Node Deleted" );
261 Event.current.Use();
262 removeNodeAtIndex( _selectedNodeIndex );
263 _selectedNodeIndex = -1;
264 }
265 }
266 }
267
268
269 if( _target.nodes.Count > 1 )
270 {
271 // allow path adjustment undo:
272 Undo.RecordObject( _target, "Path Vector Changed" );
273
274 // path begin and end labels or just one if the path is closed
275 if( Vector3.Distance( _target.nodes[0], _target.nodes[_target.nodes.Count - 1] ) == 0 )
276 {
277 Handles.Label( _target.nodes[0], " Begin and End", _labelStyle );
278 }
279 else
280 {
281 Handles.Label( _target.nodes[0], " Begin", _labelStyle );
282 Handles.Label( _target.nodes[_target.nodes.Count - 1], " End", _labelStyle );
283 }
284
285 // draw the handles, arrows and lines
286 drawRoute();
287
288 for( var i = 0; i < _target.nodes.Count; i++ )
289 {
290 Handles.color = _target.pathColor;
291
292 // dont label the first and last nodes
293 if( i > 0 && i < _target.nodes.Count - 1 )
294 Handles.Label( _target.nodes[i] + new Vector3( 3f, 0, 1.5f ), i.ToString(), _indexStyle );
295
296 Handles.color = Color.white;
297 if( _target.useStandardHandles )
298 {
299 _target.nodes[i] = Handles.PositionHandle( _target.nodes[i], Quaternion.identity );
300 }
301 else
302 {
303 // how big shall we draw the handles?
304 var distanceToTarget = Vector3.Distance( SceneView.lastActiveSceneView.camera.transform.position, _target.transform.position );
305 distanceToTarget = Mathf.Abs( distanceToTarget );
306 var handleSize = Mathf.Ceil( distanceToTarget / 75 );
307
308 _target.nodes[i] = Handles.FreeMoveHandle( _target.nodes[i],
309 Quaternion.identity,
310 handleSize,
311 new Vector3( 5, 0, 5 ),
312 Handles.SphereCap );
313 }
314
315
316 // should we snap? we need at least 4 nodes because we dont snap to the previous and next nodes
317 if( Event.current.control && _target.nodes.Count > 3 )
318 {
319 // dont even bother checking for snapping to the previous/next nodes
320 var index = getNearestNode( _target.nodes[i], i, i + 1, i - 1 );
321 var nearest = _target.nodes[index];
322 var distanceToNearestNode = Vector3.Distance( nearest, _target.nodes[i] );
323
324 // is it close enough to snap?
325 if( distanceToNearestNode <= _snapDistance )
326 {
327 GUI.changed = true;
328 _target.nodes[i] = nearest;
329 }
330 else if( distanceToNearestNode <= _snapDistance * 2 )
331 {
332 // show which nodes are getting close enough to snap to
333 var color = Color.red;
334 color.a = 0.3f;
335 Handles.color = color;
336 Handles.SphereCap( 0, _target.nodes[i], Quaternion.identity, _snapDistance * 2 );
337 //Handles.DrawWireDisc( _target.nodes[i], Vector3.up, _snapDistance );
338 Handles.color = Color.white;
339 }
340 }
341 } // end for
342
343
344 if( GUI.changed )
345 {
346 Repaint();
347 EditorUtility.SetDirty( _target );
348 }
349 } // end if
350 }
351
352 #endregion
353
354
355 #region Private methods
356
357 private void appendNodeAtPoint( Vector3 node )
358 {
359 _target.nodes.Add( node );
360
361 GUI.changed = true;
362 }
363
364
365 private void removeNodeAtIndex( int index )
366 {
367 if( index >= _target.nodes.Count || index < 0 )
368 return;
369
370 _target.nodes.RemoveAt( index );
371
372 GUI.changed = true;
373 }
374
375
376 private void insertNodeAtIndex( Vector3 node, int index )
377 {
378 // validate the index
379 if( index >= 0 && index < _target.nodes.Count )
380 {
381 _target.nodes.Insert( index, node );
382
383 GUI.changed = true;
384 }
385 }
386
387
388 private void drawArrowBetweenPoints( Vector3 point1, Vector3 point2 )
389 {
390 // no need to draw arrows for tiny segments
391 var distance = Vector3.Distance( point1, point2 );
392 if( distance < 40 )
393 return;
394
395 // we dont want to be exactly in the middle so we offset the length of the arrow
396 var lerpModifier = ( distance * 0.5f - 25 ) / distance;
397
398 Handles.color = _target.pathColor;
399
400 // get the midpoint between the 2 points
401 var dir = Vector3.Lerp( point1, point2, lerpModifier );
402 var quat = Quaternion.LookRotation( point2 - point1 );
403 Handles.ArrowCap( 0, dir, quat, 25 );
404
405 Handles.color = Color.white;
406 }
407
408
409 private int getNearestNode( Vector3 pos, params int[] excludeNodes )
410 {
411 var excludeNodesList = new System.Collections.Generic.List<int>( excludeNodes );
412 var bestDistance = float.MaxValue;
413 var index = -1;
414
415 var distance = float.MaxValue;
416 for( var i = _target.nodes.Count - 1; i >= 0; i-- )
417 {
418 if( excludeNodesList.Contains( i ) )
419 continue;
420
421 distance = Vector3.Distance( pos, _target.nodes[i] );
422 if( distance < bestDistance )
423 {
424 bestDistance = distance;
425 index = i;
426 }
427 }
428 return index;
429 }
430
431
432 private int getNearestNodeForMousePosition( Vector3 mousePos )
433 {
434 var bestDistance = float.MaxValue;
435 var index = -1;
436
437 var distance = float.MaxValue;
438 for( var i = _target.nodes.Count - 1; i >= 0; i-- )
439 {
440 var nodeToGui = HandleUtility.WorldToGUIPoint( _target.nodes[i] );
441 distance = Vector2.Distance( nodeToGui, mousePos );
442
443 if( distance < bestDistance )
444 {
445 bestDistance = distance;
446 index = i;
447 }
448 }
449
450 // make sure we are close enough to a node
451 if( bestDistance < 10 )
452 return index;
453 return -1;
454 }
455
456
457 private void closeRoute()
458 {
459 // we will use the GoSpline class to handle the dirtywork of closing the path
460 var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
461 path.closePath();
462
463 _target.nodes = path.nodes;
464
465 GUI.changed = true;
466 }
467
468
469 private void persistRouteToDisk( string path )
470 {
471 var bytes = new List<byte>();
472
473 foreach( var vec in _target.nodes )
474 {
475 bytes.AddRange( System.BitConverter.GetBytes( vec.x ) );
476 bytes.AddRange( System.BitConverter.GetBytes( vec.y ) );
477 bytes.AddRange( System.BitConverter.GetBytes( vec.z ) );
478 }
479
480 File.WriteAllBytes( path, bytes.ToArray() );
481 }
482
483
484 private void drawRoute()
485 {
486 // if we are forcing straight lines just use this setup
487 if( _target.forceStraightLinePath )
488 {
489 // draw just the route here and optional arrows
490 for( var i = 0; i < _target.nodes.Count; i++ )
491 {
492 Handles.color = _target.pathColor;
493 if( i < _target.nodes.Count - 1 )
494 {
495 Handles.DrawLine( _target.nodes[i], _target.nodes[i + 1] );
496 drawArrowBetweenPoints( _target.nodes[i], _target.nodes[i + 1] );
497 }
498 }
499 }
500 }
501
502 #endregion
503
504 }